//
// Cassandra.m
// Copyright (c) 1988, 1989, 1990, 1991, 1992 by Jiro Nakamura 
// All rights reserved
//
// Main control object of Cassandra. Routes most calls to their proper places
// and handles alarm control.
//
//	by Jiro Nakamura (jiro@shaman.com)
//
// RCS Information
// Revision Number->	$Revision: 2.19 $
// Last Revised->	$Date: 92/11/10 00:50:10 $
//
static char authorid[] = "$Author: jiro $";
static char rcsid[] = "$Id: Cassandra.m,v 2.19 92/11/10 00:50:10 jiro Exp Locker: jiro $";
static char copyrightid[] = 
	"Copyright (C) 1988, 1989, 1990, 1991 by Jiro Nakamura. "
	"All Rights Reserved.";
		
#import <stdio.h>
#import <libc.h>
#import <time.h>		
#import <sys/file.h>
#import <strings.h>
#import <appkit/Form.h>
#import <appkit/Button.h>
#import <appkit/Panel.h>       	/* for NXRunAlertPanel() 	*/
#import <appkit/publicWraps.h>  /* for NXBeep( ) 		*/
#import <appkit/Application.h> 	/* for NX_MODALRESPTHRESHOLD  	*/
#import <appkit/PageLayout.h>
#import <dpsclient/wraps.h>	
#import <soundkit/Sound.h>
#import <bsd/dev/m68k/evsio.h>	/* For screen handling */

#import "Cassandra.h"
#import "Event.h"
#import "Global.h"
#import "misc.h"
#import "calendar.h"
#import "EventLog.h"
#import "Notepad.h"
#import "Overview.h"
#import "Today.h"

#define	WHENREADY	-1.0		// for startAlarmTimedEntry
#define	NOW		0.0

#define MAXSECONDS	(24 * 60 * 60)		// 24 hours in seconds

char *getlogin();		// This isn't defined anywhere....
				// NeXT, get on your feet!

@implementation Cassandra

// We override new in order to set the delegate of the application object
// to itself. This allows the appDidInit: and appDidHide: delegate methods
// to be invoked properly.
+ new
{

	#ifdef DEBUG
		fprintf(stderr,"Cassandra being created.\n");
	#endif
  	
	self = [super new];
	[self setDelegate:self];

	alarmFree = TRUE;
	snooze = 0;
	secondsUntilEvent = 0.0;
	queueModified = TRUE;
	return self;
}

// appDidInit is called as the first thing in the run loop of the 
// application. At this point, everything is created, but we haven't entered
// the event loop yet. appDidInit first loads the global variables in
// from the defaults database, then appDidInit creates a new instance 
// Clock, and
// makes it a subview of the application icon window (obtained through
// the appIcon method of Application). It then goes about and sets the
// lockFile for cassandra, opening the windows we need, etc.
- appDidInit: app
{
	FILE *lFile;				// lockFile file descriptor
	char line1[127], tmpFile[129];
	char tmp[256];
	NXRect cvFrame;				// contentView frame
	id applicationIcon = [NXApp appIcon];	// Our beloved icon

	#ifdef DEBUG
		fprintf(stderr,"Cassandra did initialize."
				"    (From Cassandra.m)\n");
	#endif
	
	now = *timeNow();		// Set the time
	[global	load:self];		// Let's load the global variables
	
	queueModified = YES;		// Make sure queue gets re-read
	
	// Check for lockfile, if none, create one. If one, ask user
	// what he/she wants to do.
	strcpy(lockFile,[global eventFile]);	// "eventFile" + ".lck"
	strcat(lockFile,".lck");
	if( access( lockFile, F_OK ) == 0)	// if it exists
		{
		lFile = fopen(lockFile,"r");
		fgets(line1,128,lFile);
		fclose(lFile);
		
		if( NXRunAlertPanel("Event File in Use",line1,
				"Quit","Open Anyway",NULL) == 1)
			exit(1);
		}
	
	// Ok. None exists. Create the lockFile giving the current
	// username and time.
	lFile = fopen( lockFile, "w");
	fprintf(lFile,	"The Event file is still in use. "
			"It was opened by %s on %s.\n"
			"Do you want to open it anyway?", 
			NXUserName(),
			ascMyTime( (struct tm *)timeNow(), 
			[global showSeconds], 
			[global militaryTime]));
	fclose(lFile);

	// Check to see if there is a compressed EventFile
	strcpy( tmpFile, [global eventFile]);
	strcat( tmpFile, ".Z");
	if( access([global eventFile], R_OK) != 0 &&
		access( tmpFile, R_OK) == 0)	// It exists
		{
		sprintf( tmp, "/usr/ucb/uncompress %s", tmpFile);
		#ifdef DEBUG
			fprintf( stderr, "%s:", PROGNAME, tmp);
		#endif
		system( tmp);
		}

	// Set up the clock icon
	// This is from ClockApp (used w/ author's permission)
	[[applicationIcon contentView] getFrame:&cvFrame];
	NXInsetRect(&cvFrame, 3.0, 3.0);	//let 's not touch the border
	clockView = [Clock newFrame:&cvFrame];

	[[applicationIcon contentView] addSubview:clockView];
	[self defaultsDidChange:self];
	[applicationIcon display]; 	
	// then, set the target and action of Clock so we can use it.
	[clockView setTarget:	self];
	[clockView setAction:	@selector(tick:)];
	[self updateClockViewMessage: self];
	
	
	// If we should source .cassandra/login at login-time
	if( [global	sourceLoginOnLaunch] && [global autoLaunched])
		{
		sprintf(tmp, "/bin/sh %s",[global sourceLoginFile] );
		#ifdef DEBUG
			fprintf( stderr, "%s:", PROGNAME, tmp);
		#endif
		system( tmp );
		}
		
	
	// If we are opening the Overview at launch
	if( [global	overviewOnLaunch])	
		[overview open: self];

	// If we are opening the Log at launch
	if( [global	eventLogOnLaunch])	
		[eventLog open:self];
		
	// If we are opening the Notepad at launch	
	if( [global	notepadOnLaunch])	
		[notepad open:self];
		
	// If we are opening Today at launch
	if( [global	todayOnLaunch])	
		[today open:self];
	
	// If we are opening Week at launch
	if( [global	weekOnLaunch])	
		[week open:self];

	[self startAlarmTimedEntry: NOW];

	// If we should hide ourselves at launch...
	if( [global	hideOnLaunch] && [global autoLaunched] )
		[NXApp	perform:	@selector(hide:)
			with:		self
			afterDelay:	1
			cancelPrevious:	NO];

	return self;
}

//
// appDidHide is called if the user hides the application.
// appDidUnhide is called when the user unhides the application.
// These aren't implemented (just yet).
//
- appDidHide
{

	return self;
}

- appDidUnhide
{
	[overview 	timeUpdate:self];
	[today    	timeUpdate: self];
	[week		timeUpdate: self];
	
	if(  timeNow()->tm_mday != now.tm_mday)
		{
		[today update];
		[overview monthUpdate: self];
		now = *timeNow();
		}
	return self;
}

//
// Cassandra is about to quit. First, remove the lockfile,
// the close all the windows (they will save their positions
// automatically) and then quit.
//
- cassandraWillQuit:sender;
{
	char tmp[256];
	
	unlink( lockFile);	// Remove lockFile before quiting
	
	if( [global	compressEvents])
		{
		sprintf( tmp, "/usr/ucb/compress -f %s&", [global eventFile]);
		#ifdef DEBUG
			fprintf(stderr,"Compressing event file...\n");
			fprintf(stderr, "%s:", PROGNAME, tmp);
		#endif

		system( tmp);
		}

	// If we should source .cassandra/logout at logout-time
	if( [global	sourceLoginOnLaunch]  && [global autoLaunched] )
		{
		sprintf(tmp, "/bin/sh %s",[global sourceLogoutFile] );
		#ifdef DEBUG
			fprintf( stderr, "%s:", PROGNAME, tmp);
		#endif
		system( tmp );
		}
					
	if( [overview		isVisible])
		[overview 	performClose:self];
	if( [global		isVisible])
		[global 	performClose:self];

	if( [notepad		isVisible])
		[notepad 	performClose:self];
	if( [eventLog		isVisible])
		[eventLog 	performClose:self];
	if( [today 		isVisible])
		[today 		performClose: self];
	if( [week 		isVisible])
		[week 		performClose: self];
	if( [addEventPanel	isVisible])
		[addEventPanel	performClose: self];
	if( [editEventPanel	isVisible])
		[editEventPanel	performClose: self];
	
	return self;
}

//
// We want to catch the powerOff and logOut messages from the WM
// The user should manually quit Cassandra or else window
// positions will NOT be saved.
//
- appPowerOffIn: (int) ms andSave: (int) aFlag
{
	// We are powering off, oh no....
	if( aFlag == YES)	// If we are allowed to save, then
		[self cassandraWillQuit: self];
	else
		unlink( lockFile);	// At least remove the lockFile 
					// before quiting
	
	return self;
}	

//
// Brings up the information panel.
//
- info:sender
{
    if (!infoPanel) 
    	{
	infoPanel=[self loadNibSection:"InfoPanel.nib" owner:self withNames:NO];
    	}
    [infoPanel orderFront:self];
    return self;
}

//
// Brings up the help panel.
//
- help:sender
{
    if (!helpPanel) 
    	{
	helpPanel = [self loadNibSection:"HelpPanel.nib" owner:self withNames:NO];
    	}
    [helpPanel orderFront:self];
    return self;
}

//
// Brings up the Calculator window.
//
- calculator:sender
{
	if( calculatorWindow == nil)
		{
		calculatorWindow  = [self loadNibSection: "Calculator.nib" 
			owner:self withNames:NO];
    		}
    [calculatorWindow makeKeyAndOrderFront:self];
    return self;
}


// quit is called by the quit button on the main menu.
// checks to see if the user really wants to quit, then invokes
// the terminate method of itself if she or he does.
- quit: sender
{

	#ifdef ASK_IF_QUITTING
		if( NXRunAlertPanel(NULL,"Really quit?",
			"Quit","Cancel",NULL) == 1)
			{
			[self		cassandraWillQuit:self];
			[NXApp 		terminate: self];
			}
	#else
		[self		cassandraWillQuit:self];
		[NXApp 		terminate: self];
	#endif
		
	return nil;
}

// closeKeyWindow: is called by the Close button on the Window submenu
// it closes the key window (naturally).
- closeKeyWindow:sender
{
	[[self keyWindow] performClose:self];
	return self;
}

// saveKeyWindow: is called by the Save button on the Window submenu
// it saves the key window (naturally).
- saveKeyWindow:sender
{
	if( [[self keyWindow] respondsTo: @selector(save)])
		[[self keyWindow] save];
	return self;
}


// miniaturizeKeyWindow: is called by the Minituarize button on the 
// Window submenu, it miniaturizes the key window (naturally).
- miniaturizeKeyWindow:sender
{
	[[self keyWindow] performMiniaturize:self];
	return self;
}

// Runs the page layout panel
- runPageLayout: sender
{
	[[PageLayout new] runModal];
	return self;
}

- print: sender
{
	id mainWin = [NXApp keyWindow];

	if( mainWin == nil )
		NXBeep();
	
	return [mainWin  printPSCode: sender];
}


// =======================================================
//              Alarm Routines
//========================================================
// Ideally, these should be in a separate class.
// But efficiency dictates this method.
// They check the queue to see when the next event is, plant
// a timer entry for that time, then when it calls, bring
// up the alarm panel, play the jingle, etc., handle the
// anniversary events/snooze events/etc.

- alarmStart:sender
{
	Event *ev;

	#ifdef DEBUG
		fprintf(stderr,"Alarm starting\n");
	#endif
	
	// Wake up an alarm is starting
	[NXApp activateSelf: NO];	// Let's activate ourself!	
	[Sound getVolume: &oldVolumeLeft:&oldVolumeRight];
		
	ev = [Event newAt:[global eventFile]];
	[ev firstEvent];			// Read the first event

	if( [alarmPanel isVisible])	// If it is visible
		{			// then let us just 
		if( [ev playAlarm] && alarmFree && ![ev runCommand])
			 // play the alarm sound
			[self playAlarm: [ev alarmSound]]; 
		
		[[self stopAlarmTimedEntry] startAlarmTimedEntry: 3*60.0];
		[ev free];
		return self;		// repeat after three minutes
		}
	
	if( [ev	showMessage])	
		{
		[alarmPanelTime setStringValue: 
			ascMyTime([ev time], NOSEC,
			[global militaryTime]) at:0];
		[alarmPanelMessage setStringValue : [ev message]   at:0];
		[alarmPanelNextEvent setStringValue: ascMyTime( 
			fixAnniversary([ev time],  [ev anniversary], 
				[ev annvSpecial]), 
				NOSEC, [global militaryTime]) at: 0];
				
		if( [ev snoozeNo] > 1)
			[snoozeButton setEnabled :	TRUE];
		else
			[snoozeButton setEnabled :	FALSE];
	
		[snoozeButton	setState:	0];
		[okButton	setState:	0];
	
		[alarmPanel	makeKeyAndOrderFront: self];
		NXPing();
		}
	
	#ifdef DEBUG
		fprintf(stderr, "Starting alarm with: %s\n	: %s",
		   ascMyTime([ev time], NOSEC, [global militaryTime]), 
		   [ev message]);
	#endif
	
	if( alarmFree && [ev playAlarm] && ![ev runCommand] )
		[self playAlarm: [ev alarmSound]];
	else
		{
		#ifdef DEBUG
			fprintf(stderr,"Alarm is not free or no alarm.....\n");
		#endif
		if( [ev runCommand] )
			system( [ev alarmSound] );

		}
	
	[ev free];
	[self nextSnoozeAlarm];	
	
	return self;	
}

- playAlarm: (char *) alarmSound
{
	int error;
	
	// From 1.0 Docs, Sound Kit, pp22-498
	[Sound getVolume: &oldVolumeLeft:&oldVolumeRight];
	
	#ifdef DEBUG
		fprintf(stderr,"Old volume L=%f R=%f, New Volume =%f\n",
			oldVolumeLeft, oldVolumeRight, [global volume]);
	#endif

	alarmFree = FALSE;
			
//	if( index(alarmSound, '/') == (char *) 0)
		soundfile = [Sound findSoundFor:  alarmSound];
//	else
//		soundfile = [Sound newFromSoundfile: alarmSound];

	if( soundfile != nil)
		{
		[soundfile setDelegate: self];
		if( [global volume] >= 0)
			[Sound setVolume: [global volume]: 
				[global volume]];
		error = [soundfile play];
		if( error != 0)				
			{
			fprintf(stderr,"Cassandra: "
				"There was an error %d (%s)"
				" playing the alarm sound: %s.\n", 
				error, SNDSoundError(error), 
				alarmSound);
			alarmFree = TRUE;
			// Restore old volume
			[Sound	setVolume: oldVolumeLeft :
				 oldVolumeRight];
			 }
		}
	else
		{
		fprintf(stderr, 
			"%s: Couldn't find the alarm sound <%s>."
			"Substituting with obnoxious system beep.\n",
				PROGNAME, alarmSound);
		NXBeep();
		alarmFree = TRUE;
		}
	return self;
}
- hadError : sender
{
	char errorString[200];
	int error = [[sender soundBeingProcessed] processingError];	
	
	sprintf(errorString,
		"Error #%d (%s) occured while playing the alarm sound <%s>.\n", 
		error, SNDSoundError(error),
		[[sender soundBeingProcessed] name]);
					
	NXRunAlertPanel("Alarm Error",errorString, 
			"Oh well...",NULL,NULL);

	[self nextSnoozeAlarm];
 	[self queueDidChange: self];

	// Restore old volume
	[Sound	setVolume: oldVolumeLeft: oldVolumeRight];
	alarmFree = TRUE;
	[soundfile free];
	
	return self;
}


- didPlay: sender
{		

	#ifdef DEBUG
		fprintf(stderr,"Alarm Freed! Restoring volume to %f and %f\n",
			oldVolumeLeft, oldVolumeRight);
	#endif
	
	// If the alarm panel is invisible, it must mean the user
	// meant for this to be a one shot deal, thus alert
	// Cassandra that the queue changed so that it can update
	// the alarm timers and overview window
	if( ![alarmPanel isVisible])
 		[self queueDidChange: self];

		
	[snoozeButton setEnabled :	FALSE];
	alarmFree = TRUE;
	[soundfile free];

	// Restore old volume
	[Sound	setVolume: oldVolumeLeft: oldVolumeRight];

	return self;
}

- nextSnoozeAlarm
{
	EFileLink here;
	Event *ev;

	ev = [Event newAt:[global eventFile]];
	[ev firstEvent];		/* Read the first event */

	here =  [ev present];
	snooze = 0; /* Initialize it to a good number, ie. nothing */
	
	if( [ev snoozeNo] > 0)
		{
		struct tm next;
			
		#ifdef DEBUG
			fprintf(stderr,"Snooze %d times with "
				"intervals of %d minutes.\n\n",
				[ev snoozeNo], [ev snoozeInt]);
		#endif
		
		/* Decrease the number of snooze events by 1 */
		[ev setSnoozeNo : ([ev snoozeNo] -1)];			

		/* Add the snooze interval to minutes */
		/* Don't worry about overflow */
		/* insertEvent can handle that */		
		next = *[ev time];
		next.tm_min  += [ev snoozeInt];	
									
		/* Set event parameters so that the inserted one gets nixed */
		/* properly when its time is due */			
		[ev setDestroy		:1];
		[ev setAnniversary	:0];
		[ev setPriority		:1];
		[ev setTime: &next];
		
		/* Set the snooze marker so we know which event to nix later */
		/* and insert the new event */
		snooze = [ev insertEvent];
		
		// Turn the queueModified flag on so that the next
 		// timerUpdate will re-read the queue for the first event
		queueModified = TRUE;

		[ev free];
		}
	[self alarmReset: self];
	return self;
}


- alarmSnooze : sender
{
	#ifdef DEBUG
		fprintf(stderr,"Alarm snoozeing....\n");
	#endif
	
	if( [alarmPanel isVisible])
		{
		if( !alarmFree)
			{
			[soundfile stop];
			[soundfile free];
			alarmFree = TRUE;
			}
		[alarmPanel close];

		[self queueDidChange: self];

		// Restore old volume
		[Sound	setVolume: oldVolumeLeft: oldVolumeRight];
		}
	return self;
}

- alarmStop: sender
{
	if( [alarmPanel isVisible])
		{
		#ifdef DEBUG
			fprintf(stderr,"Alarm stopping.\n");
			fprintf(stderr,"Restoring volume to %f and %f\n",
				oldVolumeLeft, oldVolumeRight);
		#endif

		if( !alarmFree)
			{
			[soundfile stop];
			[soundfile free];
			alarmFree = TRUE;
			}
		[alarmPanel close];
		
		// Restore old volume
		[Sound	setVolume: oldVolumeLeft: oldVolumeRight];
		
		if( snooze > 0 )
			{
			id ev;
		
	 		ev = [Event newAt:[global eventFile]];
			[ev deleteEvent: snooze];
			[ev free];
			snooze = 0;
			}

		[self queueDidChange: self];
		}
	return self;
}


// This is connected to the Reset/Cancel Button on the main menu->
// Modify Events submenu.
- resetCancel : sender
{
	Event *ev;

	ev = [Event newAt:[global eventFile]];
	[ev firstEvent];	/* Read the first event */

	if( [ev present] == 0)
		{
		NXRunAlertPanel("Alert",
				"You cannot reset/cancel the next event "
				"since there are no events...",
				"OK", NULL, NULL);
		[ev free];
		return self;
		}
		
	if( NXRunAlertPanel(	"Alert",
				"This will reset the next event. "
				"Are you sure you want to do this?",
				"Do it","Cancel",NULL) == 1)
		{
		[self alarmReset:self];
		[self queueDidChange: self];
		}

	[ev free];
	return self;
}

// Actually reset the next event.
// Delete the first event in the queue. Log it if it has a high
// enough priority. Check if it is an anniversary event, and handle
// that.
- alarmReset: sender
{
	Event *ev;
	FILE *fd;
	int temp;
	static	struct tm lastEventTime;
	
	ev = [Event newAt:[global eventFile]];

	[ev firstEvent];		/* Read the first event */
	
	if( [ev priority] >= [global lowPriority] && [ev present] != 0)
		{
		char errormsg[128];
		
		// Ok, we are writing to the event log, just to be sure,
		// let's tell the event log object to save itself.
		[eventLog save];

		sprintf(errormsg, "Fatal error while creating "
			"the log file %s", [global eventLogFile]);
			
		// Check to make sure it exists, if it does not
		// we have to create it and stick a header on it
		if( access( [global eventLogFile], F_OK ) == -1)	
			{
			if(  ( fd = fileOpen( (char *)[global eventLogFile],
				"a", errormsg)) != NULL)
				{
				fprintf(fd, "{\\rtf0\\ansi"
					"{\\fonttbl\\f0\\fswiss "
					"Helvetica;}\n}\n");
				fclose( fd);
				}
			else
				fprintf(stderr,"%s: Couldn't create logfile\n",
					 PROGNAME);
			}
				
			
		sprintf(errormsg, "Fatal error while appending to "
			"the log file %s", [global eventLogFile]);
			
		if(  ( fd = fileOpen((char *) [global eventLogFile],"a", 
			errormsg)) != NULL)
			{
			fseek(fd, -2, SEEK_END); // Remove bracket from end
			if( 	lastEventTime.tm_mday != timeNow()->tm_mday ||
				lastEventTime.tm_mon  != timeNow()->tm_mon ||
				lastEventTime.tm_year != timeNow()->tm_year)
				{
				fprintf(fd,	"\\par\\f0\\b %s\\par\n",
					ascMyDate([ev time]));
				lastEventTime = *timeNow();
				}
				
			if( [ev priority] > [global highPriority])
				fprintf(fd, "\\f0\\b ");
			else
				fprintf(fd, "\\f0\\b0 ");

			if( [global militaryTime])
				fprintf(fd, "\t%2d:%02d\t%s"
						"\\par\n", 
					[ev hour], [ev min],
					[ev message]);
			else
				{
				temp = [ev hour];
			
				if( temp > 12 )
					temp -= 12;
				if( temp == 0 )
					temp = 12;			
			
				fprintf(fd, "\t%2d:%02d %s\t%s"
						"\\par\n", 
					temp,
					[ev min], 
					([ev hour]>11)?"pm":"am",
					[ev message]);
				}
			fprintf(fd, "}");
			fclose(fd);
			[eventLog  update];
			}
		else
			fprintf(stderr,"couldn't open file\n");

		}
	else
		{
		#ifdef DEBUG
			fprintf(stderr,"Not high enough priority to log.\n");
		#endif
		}
	
	[ev murderEvent: [ev present]];	// Kill/delete/anniverserate event
	
	// Turn the queueModified flag on so that the next timerUpdate 
	// will re-read the queue for the first event
	[self queueDidChange: self];

	[ev free];
	return self;
}

// Message   queueDidChange:sender 
// Received if the event queue has changed in any fashion.
// Tells other objects to update themselves to respond to this change.
- queueDidChange: sender
{
	
	// Notify the overview window object that the queue has changed
	[overview queueDidChange:self];
	
	// Notify the Today window to update itself just in case
	[today update];

	// Notify the Week window to update itself just in case
	[week update];
	
	// Update the clockView if necessary
	[self updateClockViewMessage: self];
	
	// Turn the queueModified flag on so that the next timerUpdate 
	// will re-read the queue for the first event
	queueModified = TRUE;
	
	// Check the next alarm, now that the flag has been set.
	[self timerUpdate:self];
	
	// And reschedule the timer entry for the next alarm.
	// Give the user ten seconds to react
	[[self stopAlarmTimedEntry] startAlarmTimedEntry: 10.0];
	
	return self;
}

- updateClockViewMessage: sender
{
	static char buf1[80], buf2[80];
	extern const char *shortMonths[12];
	
	//Check to see if we are using the snazzy digital clock, if we are
	// then we want to snarf off the first event and pipe it to Clock
	if( [global clockType] == CLOCK_DIGITAL)	
		{
		Event *ev;
		int hour;
		char *suffix;
		
		ev = [Event newAt:[global eventFile]];	
		[ev firstEvent];	// Read in the first event 

		hour = [ev hour];
		if( ![global militaryTime])
			{			
			if( hour >= 12 )
				{
				suffix = "p";
				hour -= 12;
				}
			else
				suffix = "a";
				
			if( hour == 0 )
				hour = 12;			
			}
		else
			suffix = "";
		
		sprintf(buf1,"%2d:%02d%s %s %d", hour, [ev min], suffix, 
				shortMonths[ [ev mon] ], [ev mday]);
		
		strncpy(buf2, [ev message], 20);
		[clockView setMessage: 	buf1: buf2];
		[ev free];
		}
	return self;
}

- defaultsDidChange: sender
{
	const char *str;

	#ifdef DEBUG
		fprintf(stderr, 
			"Defaults did change... App compensating.\n");
	#endif

	[overview 	defaultsDidChange: self];
	[today		defaultsDidChange: self];
	[week		defaultsDidChange: self];
  
	// Read the ShowSeconds default and process it.
	[clockView setShowSecondsToBool: [global showSeconds]];
	[clockView setShowDateToBool: [global showDate]];
	[clockView setClockTypeToInt: [global clockType]];
	[clockView setMilitaryTimeToBool: [global militaryTime]];
	[self updateClockViewMessage: self];
		
	// Read the FaceBitmap default and process that.
	str = [global clockfaceBitmapName];
	[clockView setClockFaceToBitmapNamed:(str && str[0] ? str : NULL)];

	saveScreen = [global saveScreen];
	
	return self;
}


//  Message timerUpdate: sender
// This checks the present alarm and the first event of the queue and compares
// them. If the event at the very front is equal to or greater 
// than the present time, then send off messages turning on the alarm system.
- timerUpdate :sender
{
	static struct tm nextAlarmTime;
	static int presentEventNumber;
	static BOOL backlog = NO;
	
	// Check to see if the queue has been modified/changed since the last
	// time we read. If it has, we have to re-read the first event in
	// since its time may have changed.
	if( queueModified)
		{
		Event *ev;

		ev = [Event newAt:[global eventFile]];	
		
		#ifdef DEBUG
			fprintf(stderr,"Queue modified/new. "
				"Re-reading event.\n");
		#endif

		[ev firstEvent];	// Read in the first event 
		
		/* Remember its number (place) in the queue */
		presentEventNumber = [ev present];
		
		/* and copy down the time at which it will occur */
		nextAlarmTime = *[ev time];	
		
		queueModified = FALSE;		// Reset the flag,
		[ev free];			// and tidy up
		}
	else
		{
		// If the list was not modified, we can stick with the 
		// information we
		// already have in presentEventNumber and nextAlarmTime 
		#ifdef DEBUG
			fprintf(stderr, 
				"Queue not modified. Skipping re-read.\n");
		#endif
		}

	/* How many more seconds until the event? */
	if( presentEventNumber > 0)  // Check if we're on the first event
		secondsUntilEvent =  secondsBetween(    (struct tm *) 
		timeNow(), &nextAlarmTime	);
	else
		secondsUntilEvent = 60*60*4;	// Four hours
	
	#ifdef DEBUG
		fprintf(stderr, "Time now               = %s.\n", 
			  ascMyTime((struct tm*) timeNow(),
				 SEC, [global militaryTime]));
		fprintf(stderr, "Event #%d's time = %s. \n"
			"secondsUntilEvent = %6.1f sec. (%6.1f min.)\n",
			presentEventNumber,
			ascMyTime( &nextAlarmTime,SEC, [global militaryTime]),
			secondsUntilEvent, secondsUntilEvent/60);
	#endif

	/* If it looks like the user has backlogged a lot of events, ask */
	/* her or him if she or he wants to clear all of the until today */
	if(secondsUntilEvent < (double) -(24*60*60) && presentEventNumber >0)
		{
		[NXApp unhide: self];
		if(backlog == YES || NXRunAlertPanel("Backlog Warning",
				"You have backlogged more than a day's "
				"worth of events. "
				"Would you rather clear all of them?",
				"Yes","No",NULL) == 1)
			{
			Event *ev;
			ev = [Event newAt:[global eventFile]];	
			
			backlog = YES;
			
			do
				{
				[self alarmReset:self];
				[ev firstEvent];
				}
			while(  secondsBetween((struct tm *) timeNow(),
				 [ev time]) < 0 && [ev present] > 0);
			 
			[ev free];
			[self queueDidChange:self];
			return self;
			}
		else
			backlog = NO;
		}
				

	/* If the event's time has come and it isn't a bogus event */
	/* (0 or below are ignored) then start the alarm */
	if(   secondsUntilEvent <=  0 &&  presentEventNumber > 0)
		{
		[self alarmStart:self];
		}
	else
		{
		// OK, then let us start the bloody timer again
		[self startAlarmTimedEntry: secondsUntilEvent];
		}
	return self;
}

// ===================================
//         Timer Routines
// ===================================
// These handle the timer object
// for alarm control.
// Borrowed mostly off the Animator object.

void checkAlarm(teNum, now, cassandra) 
DPSTimedEntry teNum;
double now;
id cassandra;
{
	[cassandra stopAlarmTimedEntry];
	[cassandra timerUpdate:cassandra];
}


- startAlarmTimedEntry:(double) fireWhen
{
    double fireIn;

	if( fireWhen > 0.0)
		fireIn =  fireWhen;
	else
		{
		if (fireWhen = NOW || fireWhen < 0.0) 
			fireIn = 0.0;	  // Fire as soon as possible!
		else
			{
			 fireIn = secondsUntilEvent;
			 if( fireIn > MAXSECONDS )			 										
			 	fireIn = (double) MAXSECONDS;	
			}
		}
  			
	#ifdef DEBUG
		fprintf(stderr,"Starting alarmTE in %5.2f seconds. "
				"(%d min. %d sec.)\n", fireIn,
				((int) fireIn / 60),  ((int)fireIn % 60));
	#endif
	
    alarmTE = DPSAddTimedEntry(fireIn, &checkAlarm, 
    				self, NX_MODALRESPTHRESHOLD);

    return self;
}

- stopAlarmTimedEntry
{
	#ifdef DEBUG
		fprintf(stderr,"Stopping alarmTE.\n");
	#endif
	if( alarmTE != NULL)
		DPSRemoveTimedEntry (alarmTE);
	
	alarmTE = NULL;
    return self;
}

- tick:sender;
{
	int ad =0;
	int evs;
	static dimmed = FALSE;
	static minuteNow;
	
	[overview timeUpdate:self];
	[today    timeUpdate: self];
	
	// Check things that only needed to be checked periodically.
	if( minuteNow != timeNow()->tm_min)
		{
		
		if(  timeNow()->tm_mday != now.tm_mday)
			{
			[today update];
			[overview monthUpdate: self];
			[week update];
			now = *timeNow();
			}


		if( saveScreen )
			{
			// Check state of autodimming
			evs = open( "/dev/evs0", O_RDONLY);
			ioctl(evs, EVSIOCADS, &ad);
			close(evs);
	
			if( ad )
				{
				if( dimmed == FALSE)
					[self screenDimmed: self];
				dimmed = TRUE;
				}
			else
				{
				if( dimmed == TRUE)
					[self screenUndimmed: self];
				dimmed = FALSE;
				}
			}
		}
	return self;
}

// The screen has dimmed. Call whatever program the user wants.
- screenDimmed: sender
{
	static char command[128];
	
	sprintf(command, "%s&", [global screenSaver]);
	
	#ifdef DEBUG
		fprintf(stderr,"%s: System autodimmed w/ <%s>\n", 
			PROGNAME, [global screenSaver]);
		NXBeep(); NXBeep();
	#endif
	
	system( command );
	return self;
}

- screenUndimmed: sender
{
	#ifdef DEBUG
		fprintf(stderr,"%s: Screen undimmed.\n", PROGNAME);
	#endif
	
	return self;
}

- global	{ return global; }
@end

